package com.gionee.navil.lockscreen;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

public class GnKeyguardWidgetFrame extends FrameLayout {
	private final static String TAG = "KeyguardWidgetFrame";
	private final static PorterDuffXfermode sAddBlendMode = new PorterDuffXfermode(
			PorterDuff.Mode.ADD);

	static final float OUTLINE_ALPHA_MULTIPLIER = 0.6f;
	static final int HOVER_OVER_DELETE_DROP_TARGET_OVERLAY_COLOR = 0x99FF0000;

	// Temporarily disable this for the time being until we know why the gfx is
	// messing up
	static final boolean ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY = true;

	private final static boolean ENABLE_DECORATION = false;

	private int mGradientColor;
	private LinearGradient mForegroundGradient;
	private LinearGradient mLeftToRightGradient;
	private LinearGradient mRightToLeftGradient;
	private Paint mGradientPaint = new Paint();
	boolean mLeftToRight = true;

	private float mOverScrollAmount = 0f;
	private final Rect mForegroundRect = new Rect();
	private int mForegroundAlpha = 0;
	private GnCheckLongPressHelper mLongPressHelper;
	private Animator mFrameFade;
	private boolean mIsSmall = false;
	private Handler mWorkerHandler;

	private float mBackgroundAlpha;
	private float mContentAlpha;
	private float mBackgroundAlphaMultiplier = 1.0f;
	private Drawable mBackgroundDrawable;
	private Rect mBackgroundRect = new Rect();

	// These variables are all needed in order to size things properly before
	// we're actually
	// measured.
	private int mSmallWidgetHeight;
	private int mSmallFrameHeight;
	private boolean mWidgetLockedSmall = false;
	private int mMaxChallengeTop = -1;
	private int mFrameStrokeAdjustment;

	// This will hold the width value before we've actually been measured
	private int mFrameHeight;

	private boolean mIsHoveringOverDeleteDropTarget;

	// Multiple callers may try and adjust the alpha of the frame. When a caller
	// shows
	// the outlines, we give that caller control, and nobody else can fade them
	// out.
	// This prevents animation conflicts.
	private Object mBgAlphaController;

	public GnKeyguardWidgetFrame(Context context) {
		this(context, null, 0);
	}

	public GnKeyguardWidgetFrame(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public GnKeyguardWidgetFrame(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
		mLongPressHelper = new GnCheckLongPressHelper(this);

		Resources res = context.getResources();
		// TODO: this padding should really correspond to the padding embedded
		// in the background
		// drawable (ie. outlines).
		float density = res.getDisplayMetrics().density;
		int padding = (int) (res.getDisplayMetrics().density * 8);
		setPadding(padding, padding, padding, padding);

		mFrameStrokeAdjustment = 2 + (int) (2 * density);

		// This will be overriden on phones based on the current security mode,
		// however on tablets
		// we need to specify a height.
		mSmallWidgetHeight = res
				.getDimensionPixelSize(R.dimen.zzzzz_gn_navil_small_widget_height);
		mBackgroundDrawable = res
				.getDrawable(R.drawable.zzzzz_gn_navil_widget_bg_padded);
		mGradientColor = res
				.getColor(R.color.zzzzz_gn_navil_widget_pager_gradient);
		mGradientPaint.setXfermode(sAddBlendMode);
	}

	void setIsHoveringOverDeleteDropTarget(boolean isHovering) {
		if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) {
			if (mIsHoveringOverDeleteDropTarget != isHovering) {
				mIsHoveringOverDeleteDropTarget = isHovering;
				invalidate();
			}
		}
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// Watch for longpress events at this level to make sure
		// users can always pick up this widget
		GnLog.d(TAG, "onInterceptTouchEvent");
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mLongPressHelper.postCheckForLongPress(ev);
			break;
		case MotionEvent.ACTION_MOVE:
			mLongPressHelper.onMove(ev);
			break;
		case MotionEvent.ACTION_POINTER_DOWN:
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			mLongPressHelper.cancelLongPress();
			break;
		}

		// Otherwise continue letting touch events fall through to children
		return false;
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		// Watch for longpress events at this level to make sure
		// users can always pick up this widget
		GnLog.d(TAG, "onTouchEvent");
		switch (ev.getAction()) {
		case MotionEvent.ACTION_MOVE:
			mLongPressHelper.onMove(ev);
			break;
		case MotionEvent.ACTION_POINTER_DOWN:
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			mLongPressHelper.cancelLongPress();
			break;
		}

		// We return true here to ensure that we will get cancel / up signal
		// even if none of our children have requested touch.
		return true;
	}

	@Override
	public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
		super.requestDisallowInterceptTouchEvent(disallowIntercept);
		cancelLongPress();
	}

	@Override
	public void cancelLongPress() {
		super.cancelLongPress();
		mLongPressHelper.cancelLongPress();
	}

	private void drawGradientOverlay(Canvas c) {
		mGradientPaint.setShader(mForegroundGradient);
		mGradientPaint.setAlpha(mForegroundAlpha);
		c.drawRect(mForegroundRect, mGradientPaint);
	}

	private void drawHoveringOverDeleteOverlay(Canvas c) {
		if (mIsHoveringOverDeleteDropTarget) {
			c.drawColor(HOVER_OVER_DELETE_DROP_TARGET_OVERLAY_COLOR);
		}
	}

	protected void drawBg(Canvas canvas) {
		if (mBackgroundAlpha > 0.0f) {
			Drawable bg = mBackgroundDrawable;

			bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
			bg.setBounds(mBackgroundRect);
			bg.draw(canvas);
		}
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) {
			canvas.save();
		}
		if (ENABLE_DECORATION) {
			drawBg(canvas);
		}
		super.dispatchDraw(canvas);
		drawGradientOverlay(canvas);
		if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) {
			drawHoveringOverDeleteOverlay(canvas);
			canvas.restore();
		}
	}

	/**
	 * Because this view has fading outlines, it is essential that we enable
	 * hardware layers on the content (child) so that updating the alpha of the
	 * outlines doesn't result in the content layer being recreated.
	 */
	public void enableHardwareLayersForContent() {
		View widget = getContent();
		if (widget != null) {
			widget.setLayerType(LAYER_TYPE_HARDWARE, null);
		}
	}

	/**
	 * Because this view has fading outlines, it is essential that we enable
	 * hardware layers on the content (child) so that updating the alpha of the
	 * outlines doesn't result in the content layer being recreated.
	 */
	public void disableHardwareLayersForContent() {
		View widget = getContent();
		if (widget != null) {
			widget.setLayerType(LAYER_TYPE_NONE, null);
		}
	}

	public void enableHardwareLayers() {
		setLayerType(LAYER_TYPE_HARDWARE, null);
	}

	public void disableHardwareLayers() {
		setLayerType(LAYER_TYPE_NONE, null);
	}

	public View getContent() {
		return getChildAt(0);
	}

	public int getContentAppWidgetId() {
		View content = getContent();
		if (content instanceof AppWidgetHostView) {
			return ((AppWidgetHostView) content).getAppWidgetId();
		} else {
			return AppWidgetManager.INVALID_APPWIDGET_ID;
		}
	}

	public float getBackgroundAlpha() {
		return mBackgroundAlpha;
	}

	public void setBackgroundAlphaMultiplier(float multiplier) {
		if (Float.compare(mBackgroundAlphaMultiplier, multiplier) != 0) {
			mBackgroundAlphaMultiplier = multiplier;
			invalidate();
		}
	}

	public float getBackgroundAlphaMultiplier() {
		return mBackgroundAlphaMultiplier;
	}

	public void setBackgroundAlpha(float alpha) {
		if (Float.compare(mBackgroundAlpha, alpha) != 0) {
			mBackgroundAlpha = alpha;
			invalidate();
		}
	}

	public float getContentAlpha() {
		return mContentAlpha;
	}

	public void setContentAlpha(float alpha) {
		mContentAlpha = alpha;
		View content = getContent();
		if (content != null) {
			content.setAlpha(alpha);
		}
	}

	/**
	 * Depending on whether the security is up, the widget size needs to change
	 * 
	 * @param height
	 *            The height of the widget, -1 for full height
	 */
	private void setWidgetHeight(int height) {
		boolean needLayout = false;
		View widget = getContent();
		if (widget != null) {
			LayoutParams lp = (LayoutParams) widget.getLayoutParams();
			if (lp.height != height) {
				needLayout = true;
				lp.height = height;
			}
		}
		if (needLayout) {
			requestLayout();
		}
	}

	public void setMaxChallengeTop(int top) {
		boolean dirty = mMaxChallengeTop != top;
		mMaxChallengeTop = top;
		mSmallWidgetHeight = top - getPaddingTop();
		mSmallFrameHeight = top + getPaddingBottom();
		if (dirty && mIsSmall) {
			setWidgetHeight(mSmallWidgetHeight);
			setFrameHeight(mSmallFrameHeight);
		} else if (dirty && mWidgetLockedSmall) {
			setWidgetHeight(mSmallWidgetHeight);
		}
	}

	public boolean isSmall() {
		return mIsSmall;
	}

	public void adjustFrame(int challengeTop) {
		int frameHeight = challengeTop + getPaddingBottom();
		setFrameHeight(frameHeight);
	}

	public void shrinkWidget(boolean alsoShrinkFrame) {
		mIsSmall = true;
		setWidgetHeight(mSmallWidgetHeight);

		if (alsoShrinkFrame) {
			setFrameHeight(mSmallFrameHeight);
		}
	}

	public int getSmallFrameHeight() {
		return mSmallFrameHeight;
	}

	public void shrinkWidget() {
		shrinkWidget(true);
	}

	public void setWidgetLockedSmall(boolean locked) {
		if (locked) {
			setWidgetHeight(mSmallWidgetHeight);
		}
		mWidgetLockedSmall = locked;
	}

	public void resetSize() {
		mIsSmall = false;
		if (!mWidgetLockedSmall) {
			setWidgetHeight(LayoutParams.MATCH_PARENT);
		}
		setFrameHeight(getMeasuredHeight());
	}

	public void setFrameHeight(int height) {
		mFrameHeight = height;
		mBackgroundRect.set(0, 0, getMeasuredWidth(),
				Math.min(mFrameHeight, getMeasuredHeight()));
		mForegroundRect.set(mFrameStrokeAdjustment, mFrameStrokeAdjustment,
				getMeasuredWidth() - mFrameStrokeAdjustment,
				Math.min(getMeasuredHeight(), mFrameHeight)
						- mFrameStrokeAdjustment);
		updateGradient();
		invalidate();
	}

	public void hideFrame(Object caller) {
		fadeFrame(caller, false, 0f,
				GnKeyguardWidgetPager.CHILDREN_OUTLINE_FADE_OUT_DURATION);
	}

	public void showFrame(Object caller) {
		fadeFrame(caller, true, OUTLINE_ALPHA_MULTIPLIER,
				GnKeyguardWidgetPager.CHILDREN_OUTLINE_FADE_IN_DURATION);
	}

	public void fadeFrame(Object caller, boolean takeControl, float alpha,
			int duration) {
		if (takeControl) {
			mBgAlphaController = caller;
		}

		if (mBgAlphaController != caller)
			return;

		if (mFrameFade != null) {
			mFrameFade.cancel();
			mFrameFade = null;
		}
		PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat(
				"backgroundAlpha", alpha);
		mFrameFade = ObjectAnimator.ofPropertyValuesHolder(this, bgAlpha);
		mFrameFade.setDuration(duration);
		mFrameFade.start();
	}

	private void updateGradient() {
		float x0 = mLeftToRight ? 0 : mForegroundRect.width();
		float x1 = mLeftToRight ? mForegroundRect.width() : 0;
		mLeftToRightGradient = new LinearGradient(x0, 0f, x1, 0f,
				mGradientColor, 0, Shader.TileMode.CLAMP);
		mRightToLeftGradient = new LinearGradient(x1, 0f, x0, 0f,
				mGradientColor, 0, Shader.TileMode.CLAMP);
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);

		if (!mIsSmall) {
			mFrameHeight = h;
		}

		// mFrameStrokeAdjustment is a cludge to prevent the overlay from
		// drawing outside the
		// rounded rect background.
		mForegroundRect.set(mFrameStrokeAdjustment, mFrameStrokeAdjustment, w
				- mFrameStrokeAdjustment, Math.min(h, mFrameHeight)
				- mFrameStrokeAdjustment);

		mBackgroundRect
				.set(0, 0, getMeasuredWidth(), Math.min(h, mFrameHeight));
		updateGradient();
		invalidate();
	}

	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

	void setOverScrollAmount(float r, boolean left) {
		if (Float.compare(mOverScrollAmount, r) != 0) {
			mOverScrollAmount = r;
			mForegroundGradient = left ? mLeftToRightGradient
					: mRightToLeftGradient;
			mForegroundAlpha = (int) Math.round((0.5f * r * 255));

			// We bump up the alpha of the outline to hide the fact that the
			// overlay is drawing
			// over the rounded part of the frame.
			float bgAlpha = Math.min(OUTLINE_ALPHA_MULTIPLIER + r
					* (1 - OUTLINE_ALPHA_MULTIPLIER), 1f);
			setBackgroundAlpha(bgAlpha);
			invalidate();
		}
	}

	public void onActive(boolean isActive) {
		// hook for subclasses
	}

	public boolean onUserInteraction(MotionEvent event) {
		// hook for subclasses
		return false;
	}

	public void setWorkerHandler(Handler workerHandler) {
		mWorkerHandler = workerHandler;
	}

	public Handler getWorkerHandler() {
		return mWorkerHandler;
	}
}
